#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import json
import flask
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import tempfile
import shutil
import threading
import time
import traceback

# 导入自定义模块
from downloader import TileDownloader
from converter import MBTilesConverter

# 初始化Flask应用
app = Flask(__name__, 
            static_folder='static',
            template_folder='templates')
CORS(app)  # 启用跨域支持

# 日志配置
app.config['DEBUG'] = True
import logging
logging.basicConfig(level=logging.DEBUG)
app.logger.setLevel(logging.DEBUG)

app.logger.info("Flask应用初始化完成")

# 下载任务管理
active_tasks = {}
next_task_id = 1

# 配置文件路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TILES_DIR = os.path.join(BASE_DIR, "..", "data", "tiles")
MBTILES_DIR = os.path.join(BASE_DIR, "..", "data", "mbtiles")

app.logger.info(f"静态文件目录: {os.path.abspath(app.static_folder)}")
app.logger.info(f"模板目录: {os.path.abspath(app.template_folder)}")

# 确保目录存在
for directory in [TILES_DIR, MBTILES_DIR]:
    if not os.path.exists(directory):
        os.makedirs(directory)

# 路由：主页
@app.route('/')
def index():
    app.logger.info("请求首页")
    try:
        return flask.render_template('index.html')
    except Exception as e:
        app.logger.error(f"渲染模板错误: {str(e)}")
        return f"错误: {str(e)}", 500

# 路由：静态资源
@app.route('/static/<path:filename>')
def static_files(filename):
    app.logger.info(f"请求静态文件: {filename}")
    return send_from_directory(app.static_folder, filename)

# 路由：获取所有MBTiles文件
@app.route('/api/mbtiles')
def list_mbtiles():
    files = []
    for file in os.listdir(MBTILES_DIR):
        if file.endswith('.mbtiles'):
            files.append({
                'name': os.path.splitext(file)[0],
                'file': file,
                'path': os.path.join(MBTILES_DIR, file),
                'size': os.path.getsize(os.path.join(MBTILES_DIR, file)) // 1024,
                'created': os.path.getctime(os.path.join(MBTILES_DIR, file))
            })
    return jsonify(files)

# 路由：下载区域
@app.route('/api/download', methods=['POST'])
def download_area():
    global next_task_id
    
    # 解析请求数据
    data = request.json
    min_lat = float(data.get('minLat'))
    min_lon = float(data.get('minLon'))
    max_lat = float(data.get('maxLat'))
    max_lon = float(data.get('maxLon'))
    min_zoom = int(data.get('minZoom', 1))
    max_zoom = int(data.get('maxZoom', 18))
    map_name = data.get('name', f'Map_{min_lat}_{min_lon}_{max_lat}_{max_lon}')
    description = data.get('description', '离线地图')
    tile_url = data.get('tileUrl', '')
    
    # 验证参数有效性
    if min_lat >= max_lat or min_lon >= max_lon:
        app.logger.error(f"无效的经纬度范围: {min_lat},{min_lon} - {max_lat},{max_lon}")
        return jsonify({'error': '无效的经纬度范围'}), 400
        
    if min_zoom >= max_zoom:
        app.logger.error(f"无效的缩放级别范围: {min_zoom} - {max_zoom}")
        return jsonify({'error': '无效的缩放级别范围'}), 400
    
    # 确定瓦片源类型
    source_type = "osm"  # 默认使用OSM
    if "google.com" in tile_url:
        source_type = "google" if "lyrs=m" in tile_url else "google_satellite"
    elif "arcgisonline.com" in tile_url:
        source_type = "esri" if "World_Imagery" in tile_url else "esri_ocean"
    elif "opentopomap.org" in tile_url:
        source_type = "otm"
    elif "cartodb" in tile_url:
        source_type = "carto"
    elif tile_url and "{" in tile_url:  # 自定义URL
        source_type = "custom"
    
    app.logger.info(f"开始下载任务，区域: {min_lat},{min_lon} - {max_lat},{max_lon}, 瓦片源类型: {source_type}")
    
    # 创建任务ID
    task_id = next_task_id
    next_task_id += 1
    
    # 创建临时目录
    task_tiles_dir = os.path.join(TILES_DIR, f"task_{task_id}")
    if not os.path.exists(task_tiles_dir):
        os.makedirs(task_tiles_dir)
    
    # 输出MBTiles文件路径
    output_file = os.path.join(MBTILES_DIR, f"{map_name}.mbtiles")
    
    # 创建任务状态
    active_tasks[task_id] = {
        'id': task_id,
        'status': 'preparing',
        'progress': 0,
        'total_tiles': 0,
        'downloaded_tiles': 0,
        'failed_tiles': 0,
        'start_time': time.time(),
        'end_time': None,
        'tiles_dir': task_tiles_dir,
        'output_file': output_file,
        'params': {
            'min_lat': min_lat,
            'min_lon': min_lon,
            'max_lat': max_lat,
            'max_lon': max_lon,
            'min_zoom': min_zoom,
            'max_zoom': max_zoom,
            'map_name': map_name,
            'description': description,
            'tile_url': tile_url,
            'source_type': source_type
        }
    }
    
    # 保存边界信息到文件，方便后续修复使用
    boundary_info = {
        'min_lat': min_lat,
        'min_lon': min_lon,
        'max_lat': max_lat,
        'max_lon': max_lon,
        'min_zoom': min_zoom,
        'max_zoom': max_zoom,
        'map_name': map_name,
        'description': description,
        'tile_url': tile_url,
        'source_type': source_type
    }
    
    boundary_file = os.path.join(task_tiles_dir, 'boundary.json')
    try:
        with open(boundary_file, 'w') as f:
            json.dump(boundary_info, f, indent=2)
        app.logger.info(f"已保存任务 {task_id} 的边界信息到 {boundary_file}")
    except Exception as e:
        app.logger.error(f"保存边界信息失败: {str(e)}")
    
    # 启动下载线程
    threading.Thread(target=download_thread, args=(task_id,)).start()
    
    return jsonify({
        'taskId': task_id,
        'status': 'started'
    })

# 下载线程
def download_thread(task_id):
    task = active_tasks[task_id]
    task['status'] = 'downloading'
    
    try:
        # 获取瓦片源类型
        source_type = task['params'].get('source_type', 'osm')
        tile_url = task['params']['tile_url']
        
        # 创建下载器
        if source_type == "custom" and tile_url:
            # 自定义URL模板
            downloader = TileDownloader(
                tile_url_template=tile_url,
                output_dir=task['tiles_dir'],
                max_workers=3,  # 减少并发请求数量，避免被服务器拒绝
                retries=5,      # 增加重试次数
                timeout=15,     # 增加超时时间
                source_type=source_type
            )
        else:
            # 使用预设瓦片源类型
            downloader = TileDownloader(
                output_dir=task['tiles_dir'],
                max_workers=3,
                retries=5,
                timeout=15,
                source_type=source_type
            )
        
        app.logger.info(f"已创建下载器，瓦片源: {source_type}")
        
        # 下载瓦片
        zoom_levels = range(task['params']['min_zoom'], task['params']['max_zoom'] + 1)
        total_tiles, failed_tiles = downloader.download_area(
            min_lat=task['params']['min_lat'],
            min_lon=task['params']['min_lon'],
            max_lat=task['params']['max_lat'],
            max_lon=task['params']['max_lon'],
            zoom_levels=zoom_levels
        )
        
        task['total_tiles'] = total_tiles
        task['downloaded_tiles'] = total_tiles - failed_tiles
        task['failed_tiles'] = failed_tiles
        
        app.logger.info(f"下载完成: {total_tiles-failed_tiles}/{total_tiles} 瓦片成功下载，失败: {failed_tiles}")
        
        # 检查是否有太多瓦片下载失败
        if failed_tiles > 0 and (total_tiles == 0 or failed_tiles / total_tiles > 0.5):
            task['status'] = 'failed'
            task['error'] = f"下载失败率过高: {failed_tiles}/{total_tiles} 瓦片下载失败"
            app.logger.error(f"任务 {task_id} 下载失败率过高: {failed_tiles}/{total_tiles}")
            task['end_time'] = time.time()
            return
        
        # 如果有足够的瓦片下载成功，继续转换过程
        task['status'] = 'converting'
        app.logger.info(f"开始将瓦片转换为MBTiles文件")
        
        # 转换为MBTiles
        converter = MBTilesConverter(
            tiles_dir=task['tiles_dir'],
            output_file=task['output_file'],
            name=task['params']['map_name'],
            description=task['params']['description']
        )
        
        try:
            converted_tiles = converter.convert()
            app.logger.info(f"转换完成，共转换 {converted_tiles} 个瓦片")
            
            if converted_tiles > 0:
                task['status'] = 'completed'
            else:
                task['status'] = 'failed'
                task['error'] = "未能转换任何瓦片到MBTiles文件"
                app.logger.error(f"任务 {task_id} MBTiles转换失败: 未能转换任何瓦片")
        except Exception as e:
            task['status'] = 'failed'
            task['error'] = f"MBTiles转换失败: {str(e)}"
            app.logger.error(f"任务 {task_id} MBTiles转换失败: {str(e)}")
            app.logger.error(traceback.format_exc())
        
    except Exception as e:
        task['status'] = 'failed'
        task['error'] = str(e)
        task['traceback'] = traceback.format_exc()
        app.logger.error(f"下载任务 {task_id} 失败: {str(e)}")
        app.logger.error(traceback.format_exc())
    
    # 记录结束时间
    task['end_time'] = time.time()
    app.logger.info(f"任务 {task_id} 完成，状态: {task['status']}")

# 路由：获取任务状态
@app.route('/api/tasks/<int:task_id>')
def get_task_status(task_id):
    if task_id not in active_tasks:
        return jsonify({'error': '任务不存在'}), 404
    
    task = active_tasks[task_id]
    return jsonify({
        'id': task['id'],
        'status': task['status'],
        'progress': task['progress'],
        'total_tiles': task['total_tiles'],
        'downloaded_tiles': task['downloaded_tiles'],
        'failed_tiles': task['failed_tiles'],
        'start_time': task['start_time'],
        'end_time': task['end_time'],
        'output_file': os.path.basename(task['output_file']) if 'output_file' in task else None,
        'error': task.get('error')
    })

# 路由：获取所有任务
@app.route('/api/tasks')
def get_all_tasks():
    return jsonify([{
        'id': task['id'],
        'status': task['status'],
        'progress': task['progress'],
        'total_tiles': task['total_tiles'],
        'downloaded_tiles': task['downloaded_tiles'],
        'failed_tiles': task['failed_tiles'],
        'start_time': task['start_time'],
        'end_time': task['end_time'],
        'output_file': os.path.basename(task['output_file']) if 'output_file' in task else None
    } for task in active_tasks.values()])

# 路由：下载MBTiles文件
@app.route('/api/mbtiles/<filename>')
def download_mbtiles(filename):
    return send_from_directory(MBTILES_DIR, filename, as_attachment=True)

# 路由：删除MBTiles文件
@app.route('/api/mbtiles/<filename>', methods=['DELETE'])
def delete_mbtiles(filename):
    file_path = os.path.join(MBTILES_DIR, filename)
    if not os.path.exists(file_path):
        return jsonify({'error': '文件不存在'}), 404
    
    try:
        os.remove(file_path)
        return jsonify({'status': 'success'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 路由：重试失败的任务
@app.route('/api/retry_task', methods=['POST'])
def retry_task():
    data = request.json
    task_id = data.get('taskId')
    source_type = data.get('sourceType', 'esri_ocean')
    
    if task_id not in active_tasks:
        return jsonify({'success': False, 'error': '任务不存在'}), 404
    
    # 获取原始任务
    original_task = active_tasks[task_id]
    
    # 确保任务已经完成或失败
    if original_task['status'] not in ['completed', 'failed']:
        return jsonify({'success': False, 'error': '只能重试已完成或失败的任务'}), 400
    
    # 创建新任务ID
    global next_task_id
    new_task_id = next_task_id
    next_task_id += 1
    
    # 从原任务中复制参数
    task_params = original_task['params'].copy() if 'params' in original_task else {}
    
    # 更新瓦片源类型
    task_params['source_type'] = source_type
    
    # 获取瓦片源URL
    if source_type == 'osm':
        task_params['tile_url'] = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
    elif source_type == 'otm':
        task_params['tile_url'] = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'
    elif source_type == 'esri':
        task_params['tile_url'] = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
    elif source_type == 'esri_ocean':
        task_params['tile_url'] = 'https://server.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x}'
    elif source_type == 'google':
        task_params['tile_url'] = 'https://mt{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}'
    elif source_type == 'google_satellite':
        task_params['tile_url'] = 'https://mt{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}'
    
    # 创建新任务的输出目录
    new_task_tiles_dir = os.path.join(TILES_DIR, f"task_{new_task_id}")
    if not os.path.exists(new_task_tiles_dir):
        os.makedirs(new_task_tiles_dir)
    
    # 设置新任务的MBTiles输出文件
    map_name = task_params.get('map_name', f'Map_retry_{task_id}')
    output_file = os.path.join(MBTILES_DIR, f"{map_name}_retry.mbtiles")
    
    # 创建新任务状态
    active_tasks[new_task_id] = {
        'id': new_task_id,
        'status': 'preparing',
        'progress': 0,
        'total_tiles': 0,
        'downloaded_tiles': 0,
        'failed_tiles': 0,
        'start_time': time.time(),
        'end_time': None,
        'tiles_dir': new_task_tiles_dir,
        'output_file': output_file,
        'params': task_params,
        'original_task_id': task_id  # 记录原始任务ID
    }
    
    # 保存边界信息到文件
    boundary_file = os.path.join(new_task_tiles_dir, 'boundary.json')
    try:
        with open(boundary_file, 'w') as f:
            json.dump(task_params, f, indent=2)
        app.logger.info(f"已保存重试任务 {new_task_id} 的边界信息到 {boundary_file}")
    except Exception as e:
        app.logger.error(f"保存边界信息失败: {str(e)}")
    
    # 启动下载线程
    threading.Thread(target=download_thread, args=(new_task_id,)).start()
    
    return jsonify({
        'success': True,
        'taskId': new_task_id,
        'status': 'started',
        'sourceType': source_type
    })

if __name__ == '__main__':
    # 启动Flask应用
    host = '0.0.0.0'  # 监听所有网络接口
    port = 5000
    print(f"启动服务器: http://{host}:{port}")
    app.run(host=host, port=port, debug=True) 